/*****************************************************************************
 * Copyright (C) Stacy Curl. All rights reserved.                            *
 * ------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the BSD      *
 * style license a copy of which has been included with this distribution in *
 * the LICENSE.txt file.                                                     *
 *****************************************************************************/
package picounit.features.ruby_string.delayed;

import moved.org.objectweb.asm.AnnotationVisitor;
import moved.org.objectweb.asm.Attribute;
import moved.org.objectweb.asm.Label;
import moved.org.objectweb.asm.Opcodes;
import picounit.Is;
import picounit.Should;
import picounit.Test;
import picounit.Verify;

public class DelayedMethodVisitorTest implements Test, Opcodes {
	private final DelayedMethodMutator delayedMethodVisitor;

	private final Collaborators collaborators;

	private final Should should;
	private final Verify verify;
	private final Is is;

	interface Collaborators {
		MethodInstructions instructions();
		AnnotationVisitor annotationVisitor();
	}
	
	public DelayedMethodVisitorTest(Collaborators collaborators, Should should, Verify verify,
		Is is) {

		this.should = should;
		this.verify = verify;
		this.is = is;
		this.delayedMethodVisitor = new DelayedMethodMutator(collaborators.instructions());

		this.collaborators = collaborators;
	}

	public void shouldAddAnnotationToInstructionsWhenVisitingAnAnnotation() {
		shouldAddInstruction(
			new Annotation("name", false, is.instanceOf(DelayedAnnotationMutator.class)));

		should.expectAboveWhenTheFollowingOccurs();

		verify.that(delayedMethodVisitor.visitAnnotation("name", false))
			.isTheSameAs(collaborators.annotationVisitor());
	}
	
	public void shouldAddParameterAnnotationToInstructionsWhenVisitingParameterAnnotation() {
		shouldAddInstruction(new ParameterAnnotation(12345, "name", false,
			is.instanceOf(DelayedAnnotationMutator.class)));

		should.expectAboveWhenTheFollowingOccurs();

		verify.that(delayedMethodVisitor.visitParameterAnnotation(12345, "name", false))
			.isTheSameAs(collaborators.annotationVisitor());
	}

	public void shouldAddAnnotationDefaultToInstructionsWhenVisitingAnAnnotationDefault() {
		shouldAddInstruction(new AnnotationDefault(is.instanceOf(DelayedAnnotationMutator.class)));

		should.expectAboveWhenTheFollowingOccurs();

		verify.that(delayedMethodVisitor.visitAnnotationDefault())
			.isTheSameAs(collaborators.annotationVisitor());
	}

	public void shouldAddAttributeInstructionToInstructionsWhenVisitingAnAttribute() {
		Attribute attribute = new Attribute("attribute") {};

		shouldAddInstruction(new AttributeInstruction(attribute));

		should.expectAboveWhenTheFollowingOccurs();

		delayedMethodVisitor.visitAttribute(attribute);
	}

	public void shouldAddCodeInstructionToInstructionsWhenVisitingCode() {
		shouldAddInstruction(CodeInstruction.INSTANCE);

		should.expectAboveWhenTheFollowingOccurs();

		delayedMethodVisitor.visitCode();
	}
	
	public void shouldAddEndInstructionToInstructionsWhenVisitingEnd() {
		shouldAddInstruction(EndInstruction.INSTANCE);

		should.expectAboveWhenTheFollowingOccurs();

		delayedMethodVisitor.visitEnd();
	}
	
	public void shouldAddFieldInstructionToInstructionsWhenVisitingAField() {
		shouldAddInstruction(new FieldInstruction(AALOAD, "owner", "name", "desc"));

		should.expectAboveWhenTheFollowingOccurs();

		delayedMethodVisitor.visitFieldInsn(AALOAD, "owner", "name", "desc");
	}
	
	public void shouldAddIIncInstructionToInstructionsWhenVisitingIincInsn() {
		shouldAddInstruction(new IincInstruction(12345, 54321));
		
		should.expectAboveWhenTheFollowingOccurs();
		
		delayedMethodVisitor.visitIincInsn(12345, 54321);
	}
	
	public void shouldAddInsnInstructionToInstructionsWhenVisitingInsn() {
		shouldAddInstruction(new InsnInstruction(AALOAD));
		
		should.expectAboveWhenTheFollowingOccurs();
		
		delayedMethodVisitor.visitInsn(AALOAD);
	}
	
	public void shouldAddIntInstructionToInstructionsWhenVisitingIntInsn() {
		shouldAddInstruction(new IntInstruction(AALOAD, 12345));
		
		should.expectAboveWhenTheFollowingOccurs();
		
		delayedMethodVisitor.visitIntInsn(AALOAD, 12345);
	}
	
	public void shouldAddJumpInstructionToInstructionsWhenVisitingJumpInsn() {
		Label label = new Label();
		shouldAddInstruction(new JumpInstruction(IFNE, label));
		
		should.expectAboveWhenTheFollowingOccurs();
		
		delayedMethodVisitor.visitJumpInsn(IFNE, label);
	}
	
	public void shouldAddLabelInstructionToInstructionsWhenVisitingLabel() {
		Label label = new Label();
		shouldAddInstruction(new LabelInstruction(label));
		
		should.expectAboveWhenTheFollowingOccurs();
		
		delayedMethodVisitor.visitLabel(label);
	}
	
	public void shouldAddLineNumberToInstructionsWhenVisitingLineNumber() {
		Label startLabel = new Label();

		shouldAddInstruction(new LineNumberInstruction(12345, startLabel));

		should.expectAboveWhenTheFollowingOccurs();

		delayedMethodVisitor.visitLineNumber(12345, startLabel);
	}
	
	public void shouldAddLocalVariableInstructionToInstructionsWhenVisitingLocalVariable() {
		Label start = new Label();
		Label end = new Label();
		int index = 12345;
		
		shouldAddInstruction(new LocalVariableInstruction(
			"name", "desc", "signature", start, end, index));
		
		should.expectAboveWhenTheFollowingOccurs();
		
		delayedMethodVisitor.visitLocalVariable("name", "desc", "signature", start, end, index);
	}
	
	public void shouldAddLookupSwitchInstructionToInstructionsWhenVisitingLookupSwitchInsn() {
		Label dflt = new Label();
		Label[] labels = {new Label(), new Label()};
		int[] keys = {123, 321};
		
		shouldAddInstruction(new LookupSwitchInstruction(dflt, keys, labels));
		
		should.expectAboveWhenTheFollowingOccurs();
		
		delayedMethodVisitor.visitLookupSwitchInsn(dflt, keys, labels);
	}
	
	public void shouldAddMaxsInstructionToInstructionsWhenVisitingMaxs() {
		shouldAddInstruction(new MaxsInstruction(12345, 54321));
		
		should.expectAboveWhenTheFollowingOccurs();
		
		delayedMethodVisitor.visitMaxs(12345, 54321);
	}
	
	public void shouldAddMethodInstructionToInstructionsWhenVisitingMethodInsn() {
		shouldAddInstruction(new InvokeMethodInstruction(AALOAD, "owner", "name", "desc"));
		
		should.expectAboveWhenTheFollowingOccurs();
		
		delayedMethodVisitor.visitMethodInsn(AALOAD, "owner", "name", "desc");
	}
	
	public void shouldAddMultiANewArrayInstructionToInstructionsWhenVisitingMultiANewArrayInsn() {
		shouldAddInstruction(new MultiANewArrayInstruction("desc", 12345));
		
		should.expectAboveWhenTheFollowingOccurs();
		
		delayedMethodVisitor.visitMultiANewArrayInsn("desc", 12345);
	}
	
	public void shouldAddTableSwitchInstructionToInstructionsWhenVisitingTableSwitchInsn() {
		Label[] labels = {new Label(), new Label()};
		Label dflt = new Label();
		
		shouldAddInstruction(new TableSwitchInstruction(12345, 54321, dflt, labels));
		
		should.expectAboveWhenTheFollowingOccurs();
		
		delayedMethodVisitor.visitTableSwitchInsn(12345, 54321, dflt, labels);
	}
	
	public void shouldAddTryCatchBlockInstructionToInstructionsWhenVisitingTryCatchBlock() {
		Label start = new Label();
		Label end = new Label();
		Label handle = new Label();
		
		shouldAddInstruction(new TryCatchBlockInstruction(start, end, handle, "type"));
		
		should.expectAboveWhenTheFollowingOccurs();

		delayedMethodVisitor.visitTryCatchBlock(start, end, handle, "type");
	}
	
	public void shouldAddTypeInstructionToInstructionsWhenVisitingTypeInsn() {
		shouldAddInstruction(new TypeInstruction(AALOAD, "desc"));
		
		should.expectAboveWhenTheFollowingOccurs();
		
		delayedMethodVisitor.visitTypeInsn(AALOAD, "desc");
	}
	
	public void shouldAddVarInstructionToInstructionsWhenVisitingVarInsn() {
		shouldAddInstruction(new VarInstruction(AALOAD, 12345));
		
		should.expectAboveWhenTheFollowingOccurs();
		
		delayedMethodVisitor.visitVarInsn(AALOAD, 12345);
	}

	private void shouldAddInstruction(MethodInstruction instruction) {
		should.call(collaborators.instructions().add(instruction))
			.andReturn(collaborators.instructions());
	}
}
